#include <glib.h>
#include <gtk/gtk.h>
#include <SDL.h>
#include "racanhack.h"
#include "task.h"
#include "video.h"
#include "cave.h"
#include "player.h"
#include "monster.h"
#include "monster2.h"
#include "object.h"
#include "object2.h"
#include "floor.h"
#include "los.h"
#include "lookdown.h"
#include "invent.h"
#include "main.h"

enum {
  THROWED_SPEED = 16,
  THROWED_RANGE = 8
};

struct _effect {
  struct _object *object;
  struct _player *player;
  int timelimit;
  struct _sprite *sprite;
  struct _task *task;
};

static struct _effect Effect;

static void effect_trans_state(state (*fp)(struct _task *task), struct _effect *effect);
static state effect_throwed_object(struct _task *task);
static state effect_magicmissile(struct _task *task);
static state effect_end(struct _task *task);
static state effect_delete(struct _task *task);

void object_trans_state(state (*fp)(struct _task *task), struct _object *object)
{
  if (object->task != NULL) object->task->alive = FALSE;
  object->task = task_add(fp, object);
}
 
void object_player_trans_state(state (*fp)(struct _task *task), struct _object *object, struct _player *player)
{
  struct _task *task;
  if (object->task != NULL) object->task->alive = FALSE;
  if (player->task != NULL) player->task->alive = FALSE;
  task = task_add(fp, object, player);
  object->task = task;
  player->task = task;
}

struct _object *is_object(struct _coord coord)
{
  GList *li;
  struct _object *object;
  for (li = g_list_first(curf->object_list); li != NULL; li = g_list_next(li)) {
    object = (struct _object *)li->data;
    if ((object->coord.x == coord.x) && (object->coord.y == coord.y)) {
      return object;
    };
  };
  return NULL;
}

void object_move_to_inventory(struct _object *object)
{
  curf->object_list = g_list_remove(curf->object_list, object);
  Player.inventory_list = g_list_append(Player.inventory_list, object);
  object->sprite->alive = FALSE;
  object->sprite = NULL;
}

void object_move_from_inventory(struct _object *object)
{
  Player.inventory_list = g_list_remove(Player.inventory_list, object);
  curf->object_list = g_list_append(curf->object_list, object);
  object->sprite = video_sprite_add(Z_ORDER_OBJECT);
}

state object_on_floor(struct _task *task)
{
  struct _object *object;
  object = (struct _object *)task->pointer[0];
  if (is_los_onsight(object->coord)) {
    object->sprite->surface = surface_object_on_floor[object_type(object)];
  } else {
    object->sprite->surface = NULL;
  };
}

state object_player_pickup(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  msg("You picked %s up.", object_name(object));
  if (object_type(object) == OBJECT_TYPE_ARROW) {
    GList *li;
    struct _object *arrow_in_invent; 
    for (li = g_list_first(player->inventory_list); li != NULL; li = g_list_next(li)) {
      arrow_in_invent = (struct _object *)li->data;
      if (arrow_in_invent->id == object->id) {
	arrow_in_invent->count += object->count;
	object_trans_state(object_delete, object);
	player_trans_state(player_action_end, player);
	return;
      };
    };
  };
  if (object->id == OBJECT_ID_AMUYENDOR) {
    object_move_to_inventory(object);
    object_trans_state(object_in_inventory, object);
    player_trans_state(player_get_amulet_of_yendor, player);
    return;
  };
  object_move_to_inventory(object);
  object_trans_state(object_in_inventory, object);
  player_trans_state(player_action_end, player);
  return;
}

state object_player_wield(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  struct _coord c;
  struct _monster *monster;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  c = coord_dd(player->coord, player->dir);
  if (is_los_onsight(c)) {
    monster = is_monster(c);
    if (monster != NULL) {
      monster_take_hit(monster, damage_calc_wielded(monster, object, player));
    };
  };
  object_trans_state(object_in_inventory, object);
  player_trans_state(player_action_end, player);
  return;
}

state object_player_equip(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  switch (object_type(object)) {
  case OBJECT_TYPE_WEAPON:
    if (can_takeoff(player->weapon, player)) {
      if (object != player->weapon) {
	equip(object, player);
      } else {
	takeoff(player->weapon, player);
      };
    } else {
      msg("You can't. Your weapon is cursed.");
    };      
    break;
  case OBJECT_TYPE_SHIELD:
    if (can_takeoff(player->shield, player)) {
      if (object != player->shield) {
	equip(object, player);
      } else {
	takeoff(player->shield, player);
      };
    } else {
      msg("You can't. Your shield is cursed.");
    };      
    break;
  case OBJECT_TYPE_ARROW:
    if (can_takeoff(player->arrow, player)) {
      if (object != player->arrow) {
	equip(object, player);
      } else {
	takeoff(player->arrow, player);
      };
    } else {
      msg("You can't. Your arrow is cursed.");
    };      
    break;
  case OBJECT_TYPE_RING:
    if (can_takeoff(player->ring, player)) {
      if (object != player->ring) {
	equip(object, player);
      } else {
	takeoff(player->ring, player);
      };
    } else {
      msg("You can't. Your ring is cursed.");
    };      
    break;
  default:
    msg("You can't equip the %s.", object_name(object));
    break;
  };
  object_move_to_inventory(object);
  object_trans_state(object_in_inventory, object);
  player_trans_state(player_action_end, player);
  return;
}

state object_player_throw(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  struct _monster *monster;
  int i;
  struct _coord c;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  if (task->count == 0) {
    if (can_takeoff(object, player)) {
      takeoff(object, player);
      msg("You throwed %s.", object_name(object));
      c = player->coord;
      for(i = 0; i < THROWED_RANGE; i++) {
	c = coord_dd(c, player->dir);
	if (is_map_wall(c)) break;
	if (is_monster(c)) break;
      };
      object->coord = c;
      Effect.object = object;
      Effect.player = player;
      Effect.timelimit = i * (SPRITE_W / THROWED_SPEED);
      effect_trans_state(effect_throwed_object, &Effect);
      return;
    } else {
      msg("You can't take off it from your hands.");
      object_move_to_inventory(object);
      object_trans_state(object_in_inventory, object);
      player_trans_state(player_action_start, player);
      return;
    };
  };
  if (Effect.task->fp != effect_end) return;
  effect_trans_state(effect_delete, &Effect);
  if (is_map_wall(object->coord)) {
    object->coord = coord_dd(object->coord, opposite(player->dir));
    object->sprite->x = SPRITE_W * object->coord.x;
    object->sprite->y = SPRITE_H * object->coord.y;
    object_trans_state(object_on_floor, object);
    player_trans_state(player_action_end, player);
    return;
  };
  monster = is_monster(object->coord);
  if (monster != NULL) {
    switch (object->id) {
    case OBJECT_ID_HEAL_POTION:
      monster->chp += monster_mhp(monster) / 2;
      monster->chp = CLAMP(monster->chp, 0, monster_mhp(monster));
      msg("%s HP is healed.", monster_name(monster));
      break;
    case OBJECT_ID_POISON_POTION:
      monster->status.type = STATUS_POISON;
      monster->status.time = 50;
      msg("%s get poisoned.", monster_name(monster));
      break;
    case OBJECT_ID_EXHEAL_POTION:
      monster->chp = monster_mhp(monster);
      msg("%s HP is completely healed.", monster_name(monster));
      break;
    case OBJECT_ID_BLINDNESS_POTION:
      monster->status.type = STATUS_BLIND;
      monster->status.time = 50;
      msg("%s get blinded.", monster_name(monster));
      break;
    case OBJECT_ID_TELEPORT_SCROLL:
      monster->coord = rand_coord();
      msg("%s teleported.", monster_name(monster));
      break;
    default:
      monster_take_hit(monster, damage_calc_throwed(monster, object, player));
      break;
    };
    object_trans_state(object_delete, object);
    player_trans_state(player_action_end, player);
    return;
  };
  object->sprite->x = SPRITE_W * object->coord.x;
  object->sprite->y = SPRITE_H * object->coord.y;
  object_trans_state(object_on_floor, object);
  player_trans_state(player_action_end, player);
  return;
}

state object_player_apply(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  switch (object->id) {
  case OBJECT_ID_HEAL_POTION:
    player->chp = CLAMP(player->chp + (player->mhp / 2), 0, player->mhp);
    msg("Your wounds are healed.");
    break;
  case OBJECT_ID_INCSTR_POTION:
    if (player->cstr == player->mstr) player->mstr++;
    player->cstr = CLAMP(player->cstr + 1, 0, player->mstr);
    msg("Your strength increased 1 point.");
    break;
  case OBJECT_ID_RESSTR_POTION:
    player->cstr = player->mstr;
    msg("Your strength was restored.");
    break;
  case OBJECT_ID_POISON_POTION:
    player->status.type = STATUS_POISON;
    player->status.time = 50;
    msg("You get poisoned.");
    break;
  case OBJECT_ID_EXHEAL_POTION:
    player->chp = player->mhp;
    msg("Your HP is full.");
    break;
  case OBJECT_ID_BLINDNESS_POTION:
    player->status.type = STATUS_BLIND;
    player->status.time = 10;
    msg("You get blinded.");
    break;
  case OBJECT_ID_RAISELEV_POTION:
    player->exp = lev_exp_table[player->lev];
    msg("You feel more experienced.");
    break;
  case OBJECT_ID_IDENT_SCROLL:
    if (selected_object_apply != NULL) {
      set_object_ident(selected_object_apply, TRUE);
      msg("You found out %s.", object_name(selected_object_apply));
    } else {
      msg("The scroll disintegrates.");
    };
    break;
  case OBJECT_ID_RMCURSE_SCROLL:
    if (selected_object_apply != NULL) {
      if (selected_object_apply->plus < 0) selected_object_apply->plus = 0;
      msg("Removed curse from %s.", object_name(selected_object_apply));
    } else {
      msg("The scroll disintegrates.");
    };
    break;
  case OBJECT_ID_ENCWEAPON_SCROLL:
    if (player->weapon != &Noweapon) {
      msg("Your weapon is enchanced.");
      player->weapon->plus += 1;
    } else {
      msg("Your hands twitch.");
    };
    break;
  case OBJECT_ID_ENCSHIELD_SCROLL:
    if (player->shield != &Noshield) {
      msg("Your shield is enchanced.");
      player->shield->plus += 1;
    } else {
      msg("Your skin glows then fades.");
    };
    break;
  case OBJECT_ID_EXPLOSION_SCROLL:
    if (at_room(player->coord) != NULL) {
      GList *li;
      struct _monster *monster;
      msg("EXPLOSION HAS OCCURED!");
      for (li = g_list_first(curf->monster_list); li != NULL; li = g_list_next(li)) {
	monster = (struct _monster *)li->data;
	if (at_room(monster->coord) == at_room(player->coord)) {
	  monster_take_hit(monster, 25);
	};
      };
    } else {
      msg("The scroll catches fire.");
    };
    break;
  case OBJECT_ID_MONDETECT_SCROLL:
    Lookdown.mondetect = TRUE;
    msg("You sense the presence of monsters.");
    break;
  case OBJECT_ID_OBJDETECT_SCROLL:
    Lookdown.objdetect = TRUE;
    msg("You sense the presence of objects.");
    break;
  case OBJECT_ID_CREATE_MON_SCROLL:
    {
      struct _monster *monster;
      int i;
      struct _coord c;
      monster = monster_add();
      for (i = 0; i < 8; i++) {
	if (is_passable(player->coord, ddd(i))) {
	  c = coord_dd(player->coord, ddd(i));
	  break;
	} else {
	  if (i == 7) {
	    c = rand_coord();
	    break;
	  };
	};
      };
      monster->coord = c;
      monster->sprite = video_sprite_add(Z_ORDER_MONSTER);
      monster->sprite->x = monster->coord.x * SPRITE_W;
      monster->sprite->y = monster->coord.y * SPRITE_H;
      monster_trans_state(monster_turn_end, monster);
      msg("%s has popped up!", monster_name(monster)); 
    };
    break;
  case OBJECT_ID_TELEPORT_SCROLL:
    player->coord = rand_coord();
    player->sprite->x = player->coord.x * SPRITE_W;
    player->sprite->y = player->coord.y * SPRITE_H;
    msg("You teleported.");
    break;
  case OBJECT_ID_MAP_SCROLL:
    Lookdown.mapdetect = TRUE;
    msg("A map coalesces in your mind!");
    break;
  case OBJECT_ID_TELEPORT_WAND:
  case OBJECT_ID_CONFUSE_WAND:
  case OBJECT_ID_MMISSILE_WAND:
  case OBJECT_ID_SLEEP_WAND:
    object_player_trans_state(object_player_magic, object, player);
    return;
    break;
  case OBJECT_ID_RATION:
    player->food = PLAYER_FOOD_MAX;
    player->food_count = 0;
    msg("Tasty.");
    break;    
  case OBJECT_ID_SLIMEMOLD:
    player->food = CLAMP(player->food + 50, 0, PLAYER_FOOD_MAX);
    player->food_count = 0;
    msg("Oh my yummy slimemold!");
    break;
  default:
    msg("Nothing occurd.");
    object_move_to_inventory(object);
    object_trans_state(object_in_inventory, object);
    player_trans_state(player_action_end, player);
    return;
    break;
  };
  set_object_ident(object, TRUE);
  object_trans_state(object_delete, object);
  player_trans_state(player_action_end, player);
  return;
}

state object_player_drop(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  if (can_takeoff(object, player)) {
    takeoff(object, player);
    msg("You dropped %s.", object_name(object));
    object->coord = player->coord;
    object->sprite->x = SPRITE_W * object->coord.x;
    object->sprite->y = SPRITE_H * object->coord.y;
    object_trans_state(object_on_floor, object);
    player_trans_state(player_action_end, player);
    return;
  } else {
    msg("You can't take off it from your body.");
    object_move_to_inventory(object);
    object_trans_state(object_in_inventory, object);
    player_trans_state(player_action_end, player);
    return;
  };
}

state object_player_magic(struct _task *task)
{
  struct _object *object;
  struct _player *player;
  struct _monster *monster;
  int i;
  struct _coord c;
  object = (struct _object *)task->pointer[0];
  player = (struct _player *)task->pointer[1];
  if (task->count == 0) {
    g_assert(object->count > 0);
    msg("You applyed %s.", object_name(object));
    set_object_ident(object, TRUE);
    object->count--;
    c = player->coord;
    for(i = 0; i < THROWED_RANGE; i++) {
      c = coord_dd(c, player->dir);
      if (is_map_wall(c)) break;
      if (is_monster(c)) break;
    };
    object->coord = c;
    Effect.object = object;
    Effect.player = player;
    Effect.timelimit = i * (SPRITE_W / THROWED_SPEED);
    effect_trans_state(effect_magicmissile, &Effect);
    return;
  };
  if (Effect.task->fp != effect_end) return;
  effect_trans_state(effect_delete, &Effect);
  monster = is_monster(object->coord);
  if (monster != NULL) {
    switch (object->id) {
    case OBJECT_ID_TELEPORT_WAND:
      monster->coord = rand_coord();
      msg("%s teleported.", monster_name(monster));
      break;
    case OBJECT_ID_CONFUSE_WAND:
      monster->status.type = STATUS_CONFUSE;
      monster->status.time = 50;
      msg("%s get confused.", monster_name(monster));
      break;
    case OBJECT_ID_MMISSILE_WAND:
      msg("The magic missile attacked %s!", monster_name(monster));
      monster_take_hit(monster, 10);
      break;
    case OBJECT_ID_SLEEP_WAND:
      monster->status.type = STATUS_SLEEP;
      monster->status.time = 10;
      msg("%s get slept.", monster_name(monster));
      break;
    default:
      monster_take_hit(monster, damage_calc_throwed(monster, object, player));
      break;
    };
  };
  if (object->count > 0) {
    object_move_to_inventory(object);
    object_trans_state(object_in_inventory, object);
  } else {
    msg("The %s have snapped.", object_name(object));
    object_trans_state(object_delete, object);
  };
  player_trans_state(player_action_end, player);
  return;
}

state object_in_inventory(struct _task *task)
{
  struct _object *object;
  object = (struct _object *)task->pointer[0];
  object->task->alive = FALSE;
  object->task = NULL;
}

state object_delete(struct _task *task)
{
  struct _object *object;
  object = (struct _object *)task->pointer[0];
  curf->object_list = g_list_remove(curf->object_list, object);
  object->sprite->alive = FALSE;
  object->sprite = NULL;
  task->alive = FALSE;
  my_free(object);
  return;
}

static void effect_trans_state(state (*fp)(struct _task *task), struct _effect *effect)
{
  if (effect->task != NULL) effect->task->alive = FALSE;
  effect->task = task_add(fp, effect);
}

static state effect_throwed_object(struct _task *task)
{
  struct _effect *effect;
  effect = (struct _effect *)task->pointer[0];
  if (task->count == 0) {
    effect->sprite = video_sprite_add(Z_ORDER_EFFECT);
    effect->sprite->x = SPRITE_W * effect->player->coord.x;
    effect->sprite->y = SPRITE_H * effect->player->coord.y;
    effect->sprite->surface = surface_object_on_floor[object_type(effect->object)];
  };
  effect->sprite->x += THROWED_SPEED * ddx(effect->player->dir);
  effect->sprite->y += THROWED_SPEED * ddy(effect->player->dir);
  if (task->count >= effect->timelimit) {
    effect_trans_state(effect_end, effect);
  };
}

static state effect_magicmissile(struct _task *task)
{
  struct _effect *effect;
  effect = (struct _effect *)task->pointer[0];
  if (task->count == 0) {
    effect->sprite = video_sprite_add(Z_ORDER_EFFECT);
    effect->sprite->x = SPRITE_W * effect->player->coord.x;
    effect->sprite->y = SPRITE_H * effect->player->coord.y;
  };
  effect->sprite->x += THROWED_SPEED * ddx(effect->player->dir);
  effect->sprite->y += THROWED_SPEED * ddy(effect->player->dir);
  effect->sprite->surface = surface_magic[(task->count / 4) % 2];
  if (task->count >= effect->timelimit) {
    effect_trans_state(effect_end, effect);
  };
}

static state effect_end(struct _task *task)
{
}

static state effect_delete(struct _task *task)
{
  struct _effect *effect;
  effect = (struct _effect *)task->pointer[0];
  effect->sprite->alive = FALSE;
  effect->sprite = NULL;
  task->alive = FALSE;
  effect->task = NULL;
}

